﻿using System;

namespace Strategy
{
    /// <summary>
    /// Интерфейс «Стратегия» определяет функциональность (в данном примере это метод
    /// <see cref="Algorithm">Algorithm</see>), которая должна быть реализована
    /// конкретными классами стратегий. Другими словами, метод интерфейса определяет
    /// решение некой задачи, а его реализации в конкретных классах стратегий определяют,
    /// КАК, КАКИМ ПУТЁМ эта задача будет решена.
    /// </summary>
    public interface IStrategy
    {
        void Algorithm();
    }

    /// <summary>
    /// Первая конкретная реализация-стратегия.
    /// </summary>
    public class ConcreteStrategy1 : IStrategy
    {
        public void Algorithm()
        {
            Console.WriteLine("Выполняется алгоритм стратегии 1.");
        }
    }

    /// <summary>
    /// Вторая конкретная реализация-стратегия.
    /// Реализаций может быть сколько угодно много.
    /// </summary>
    public class ConcreteStrategy2 : IStrategy
    {
        public void Algorithm()
        {
            Console.WriteLine("Выполняется алгоритм стратегии 2.");
        }
    }

    /// <summary>
    /// Контекст, использующий стратегию для решения своей задачи.
    /// </summary>
    public class Context
    {
        /// <summary>
        /// Ссылка на интерфейс <see cref="IStrategy">IStrategy</see>
        /// позволяет автоматически переключаться между конкретными реализациями
        /// (другими словами, это выбор конкретной стратегии).
        /// </summary>
        private IStrategy _strategy;

        /// <summary>
        /// Конструктор контекста.
        /// Инициализирует объект стратегией.
        /// </summary>
        /// <param name="strategy">
        /// Стратегия.
        /// </param>
        public Context(IStrategy strategy)
        {
            _strategy = strategy;
        }

        /// <summary>
        /// Метод для установки стратегии.
        /// Служит для смены стратегии во время выполнения.
        /// В C# может быть реализован также как свойство записи.
        /// </summary>
        /// <param name="strategy">
        /// Новая стратегия.
        /// </param>
        public void SetStrategy(IStrategy strategy)
        {
            _strategy = strategy;
        }

        /// <summary>
        /// Некоторая функциональность контекста, которая выбирает
        /// стратегию и использует её для решения своей задачи.
        /// </summary>
        public void ExecuteOperation()
        {
            _strategy.Algorithm();
        }
    }

    /// <summary>
    /// Класс приложения.
    /// В данном примере выступает как клиент контекста.
    /// </summary>
    public static class Program
    {
        /// <summary>
        /// Точка входа в программу.
        /// </summary>
        public static void Main()
        {
            // Создём контекст и инициализируем его первой стратегией.
            Context context = new Context(new ConcreteStrategy1());
            // Выполняем операцию контекста, которая использует первую стратегию.
            context.ExecuteOperation();
            // Заменяем в контексте первую стратегию второй.
            context.SetStrategy(new ConcreteStrategy2());
            // Выполняем операцию контекста, которая теперь использует вторую стратегию.
            context.ExecuteOperation();

            Console.ReadLine();
        }
    }
}

namespace Strategy.BubbleSorter
{
    public class BubbleSorter
    {
        private int operations = 0;
        private int length = 0;
        private ISortHandler itsSortHandler = null;

        public BubbleSorter(ISortHandler handler)
        {
            itsSortHandler = handler;
        }

        public int Sort(object array)
        {
            itsSortHandler.SetArray(array);
            length = itsSortHandler.Length();
            operations = 0;
            if (length <= 1)
                return operations;

            for (int nextToLast = length - 2;
                 nextToLast >= 0;
                 nextToLast--)
                for (int index = 0; index <= nextToLast; index++)
                {
                    if (itsSortHandler.OutOfOrder(index))
                        itsSortHandler.Swap(index);
                    operations++;
                }

            return operations;
        }
    }

    public interface ISortHandler
    {
        void Swap(int index);
        bool OutOfOrder(int index);
        int Length();
        void SetArray(object array);
    }

    /// <summary>
    /// Конкретная реализация-стратегия. Сортировка массива целых чисел
    /// </summary>
    public class IntSortHandler : ISortHandler
    {
        private int[] array = null;

        public void Swap(int index)
        {
            int temp = array[index];
            array[index] = array[index + 1];
            array[index + 1] = temp;
        }

        public void SetArray(object array)
        {
            this.array = (int[])array;
        }

        public int Length()
        {
            return array.Length;
        }

        public bool OutOfOrder(int index)
        {
            return (array[index] > array[index + 1]);
        }
    }

    /// <summary>
    /// Конкретная реализация-стратегия. Сортировка массива чисел двойной точности
    /// </summary>
    public class DoubleSortHandler : ISortHandler
    {
        private double[] array = null;

        public void Swap(int index)
        {
            double temp = array[index];
            array[index] = array[index + 1];
            array[index + 1] = temp;
        }

        public void SetArray(object array)
        {
            this.array = (double[])array;
        }

        public int Length()
        {
            return array.Length;
        }

        public bool OutOfOrder(int index)
        {
            return (array[index] > array[index + 1]);
        }
    }

    /// <summary>
    /// Контекст, использующий стратегию для решения своей задачи.
    /// </summary>
    public class QuickBubbleSorter
    {
        private int operations = 0;
        private int length = 0;
        private ISortHandler itsSortHandler = null;

        public QuickBubbleSorter(ISortHandler handler)
        {
            itsSortHandler = handler;
        }

        public int Sort(object array)
        {
            itsSortHandler.SetArray(array);
            length = itsSortHandler.Length();
            operations = 0;
            if (length <= 1)
                return operations;

            bool thisPassInOrder = false;
            for (int nextToLast = length - 2;
                 nextToLast >= 0 && !thisPassInOrder;
                 nextToLast--)
            {
                thisPassInOrder = true; //potenially.
                for (int index = 0; index <= nextToLast; index++)
                {
                    if (itsSortHandler.OutOfOrder(index))
                    {
                        itsSortHandler.Swap(index);
                        thisPassInOrder = false;
                    }
                    operations++;
                }
            }

            return operations;
        }
    }

    public class StrategyTester
    {
        //public static void Main(string[] args)
        //{
        //    var sorter = new QuickBubbleSorter(new IntSortHandler());
        //    var intArray = new[] { 3, 1, 8, 4 };
        //    sorter.Sort(intArray);
        //    Printer.Print(intArray);

        //    sorter = new QuickBubbleSorter(new DoubleSortHandler());
        //    var doubleArray = new[] { 0.924314, 0.41414D, 0.632462465D, 0.142415D };
        //    sorter.Sort(doubleArray);
        //    Printer.Print(doubleArray);
        
        //    Console.ReadLine();
        //}

        private class Printer
        {
            public static void Print<T>(T[] array)
            {
                Console.WriteLine();
                foreach (var o in array)
                {
                    Console.WriteLine(o);
                }
            }
        }
    }
}